
package com.ctsi.commons.conversion;

import com.ctsi.commons.util.ObjectType;
import com.ctsi.commons.util.StringUtil;
import com.ctsi.commons.util.UtilGenerics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/** 转换器工厂. */

public class Converters {
	private static final Logger log = LoggerFactory.getLogger(Converters.class);
	protected static final String DELIMITER = "->";
	protected static final ConcurrentMap<String, Converter<?, ?>> converterMap = new ConcurrentHashMap<String, Converter<?, ?>>();
	protected static final Set<ConverterCreator> creators = new HashSet<ConverterCreator>();
	protected static final Set<String> noConversions = new HashSet<String>();

	static {
		registerCreator(new PassThruConverterCreator());
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		Iterator<ConverterLoader> converterLoaders = ServiceLoader.load(ConverterLoader.class, loader).iterator();
		while (converterLoaders.hasNext()) {
			try {
				ConverterLoader converterLoader = converterLoaders.next();
				log.debug("load :{}", converterLoader.getClass());
				converterLoader.loadConverters();
			} catch (Exception e) {
				log.error("", e);
			}
		}
	}

	private Converters() {
	}

	/**
	 * Returns an appropriate <code>Converter</code> instance for <code>sourceClass</code> and <code>targetClass</code>. If no matching <code>Converter</code>
	 * is found, the method throws <code>ClassNotFoundException</code>.
	 *
	 * <p>
	 * This method is intended to be used when the source or target <code>Object</code> types are unknown at compile time. If the source and target
	 * <code>Object</code> types are known at compile time, then one of the "ready made" converters should be used.
	 * </p>
	 *
	 * @param sourceClass
	 *            The object class to convert from
	 * @param targetClass
	 *            The object class to convert to
	 * @return A matching <code>Converter</code> instance
	 * @throws ClassNotFoundException
	 */
	public static <S, T> Converter<S, T> getConverter(final Class<S> sourceClass,final  Class<T> targetClass) throws ClassNotFoundException {
		final String key = StringUtil.concatString(sourceClass != null?sourceClass.getName():"<null>",DELIMITER,targetClass.getName());

		log.trace("Getting converter: {}", key);

		OUTER: do {
			Converter<?, ?> result = converterMap.get(key);
			if (result != null) {
				return UtilGenerics.cast(result);
			}
			if (noConversions.contains(key)) {
				throw new ClassNotFoundException("No converter found for " + key);
			}
			Class<?> foundSourceClass = null;
			Converter<?, ?> foundConverter = null;
			for (Converter<?, ?> value : converterMap.values()) {
				if (value.canConvert(sourceClass, targetClass)) {
					// this converter can deal with the source/target pair
					if (foundSourceClass == null || foundSourceClass.isAssignableFrom(value.getSourceClass())) {
						// remember the current target source class; if we find another converter, check
						// to see if it's source class is assignable to this one, and if so, it means it's
						// a child class, so we'll then take that converter.
						foundSourceClass = value.getSourceClass();
						foundConverter = value;
					}
				}
			}
			if (foundConverter != null) {
				converterMap.putIfAbsent(key, foundConverter);
				continue OUTER;
			}
			for (ConverterCreator value : creators) {
				result = createConverter(value, sourceClass, targetClass);
				if (result != null) {
					converterMap.putIfAbsent(key, result);
					continue OUTER;
				}
			}
			boolean addedToSet = false;
			synchronized (noConversions) {
				addedToSet = noConversions.add(key);
			}
			if (addedToSet) {
				log.warn("*** No converter found, converting from " + sourceClass.getName() + " to " + targetClass.getName() + ". Please report this message to the developer community so " + "a suitable converter can be created. ***");
			}
			throw new ClassNotFoundException("No converter found for " + key);
		} while (true);
	}

	private static <S, SS extends S, T, TT extends T> Converter<SS, TT> createConverter(ConverterCreator creater, Class<SS> sourceClass, Class<TT> targetClass) {
		return creater.createConverter(sourceClass, targetClass);
	}

	/**
	 * Load all classes that implement <code>Converter</code> and are contained in <code>containerClass</code>.
	 *
	 * @param containerClass
	 */
	public static void loadContainedConverters(Class<?> containerClass) {
		// This only returns -public- classes and interfaces
		for (Class<?> clz : containerClass.getClasses()) {
			try {
				// non-abstract, which means no interfaces or abstract classes
				if ((clz.getModifiers() & Modifier.ABSTRACT) == 0) {
					Object value;
					try {
						value = clz.getConstructor().newInstance();
					} catch (NoSuchMethodException e) {
						// ignore this, as this class might be some other helper class,
						// with a non-pubilc constructor
						continue;
					}
					if (value instanceof ConverterLoader) {
						ConverterLoader loader = (ConverterLoader) value;
						loader.loadConverters();
					}
				}
			} catch (Exception e) {
				log.error("", e);
			}
		}
	}

	/**
	 * Registers a <code>ConverterCreater</code> instance to be used by the {@link Converters#getConverter(Class, Class)}
	 * method, when a converter can't be found.
	 *
	 * @param <S>
	 *            The source object type
	 * @param <T>
	 *            The target object type
	 * @param creator
	 *            The <code>ConverterCreater</code> instance to register
	 */
	public static <S, T> void registerCreator(ConverterCreator creator) {
		synchronized (creators) {
			creators.add(creator);
		}
	}

	/**
	 * Registers a <code>Converter</code> instance to be used by the {@link Converters#getConverter(Class, Class)} method.
	 *
	 * @param <S>
	 *            The source object type
	 * @param <T>
	 *            The target object type
	 * @param converter
	 *            The <code>Converter</code> instance to register
	 */
	public static <S, T> void registerConverter(final Converter<S, T> converter) {
		registerConverter(converter, converter.getSourceClass(), converter.getTargetClass());
	}

	public static <S, T> void registerConverter(final Converter<S, T> converter,final  Class<?> sourceClass,final  Class<?> targetClass) {

		final String key = StringUtil.concatString(sourceClass != null?sourceClass.getName():"<null>",DELIMITER,targetClass.getName());  
		if (converterMap.putIfAbsent(key, converter) == null) {
			log.trace("Registered converter {}", converter.getClass());
		}
	}

	protected static class PassThruConverterCreator implements ConverterCreator {
		protected PassThruConverterCreator() {
		}

		public <S, T> Converter<S, T> createConverter(Class<S> sourceClass, Class<T> targetClass) {
			if (ObjectType.instanceOf(sourceClass, targetClass)) {
				return new PassThruConverter<S, T>(sourceClass, targetClass);
			} else {
				return null;
			}
		}
	}

	/**
	 * Pass thru converter used when the source and target java object types are the same. The <code>convert</code> method returns the source object.
	 *
	 */
	protected static class PassThruConverter<S, T> implements Converter<S, T> {
		private final Class<S> sourceClass;
		private final Class<T> targetClass;

		public PassThruConverter(Class<S> sourceClass, Class<T> targetClass) {
			this.sourceClass = sourceClass;
			this.targetClass = targetClass;
		}

		public boolean canConvert(Class<?> sourceClass, Class<?> targetClass) {
			return this.sourceClass == sourceClass && this.targetClass == targetClass;
		}

		@SuppressWarnings("unchecked")
		public T convert(S obj) throws ConversionException {
			return (T) obj;
		}

		@SuppressWarnings("unchecked")
		public T convert(Class<? extends T> targetClass, S obj) throws ConversionException {
			return (T) obj;
		}

		public Class<?> getSourceClass() {
			return sourceClass;
		}

		public Class<?> getTargetClass() {
			return targetClass;
		}
	}
}
